package ycl.security.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ycl.common.constants.ExceptionConstant;
import ycl.common.constants.RedisConstant;
import ycl.common.constants.SecurityConstant;
import ycl.common.enums.UserTypeEnum;
import ycl.common.exception.LoginException;
import ycl.common.models.req.login.RegisterByMobilePassReq;
import ycl.common.models.req.login.UsernamePasswordReq;
import ycl.common.models.res.LoginUser;
import ycl.common.utils.BeanConvertUtil;
import ycl.common.utils.ServletUtil;
import ycl.security.service.LoginService;
import ycl.system.entity.SysUser;
import ycl.system.mapper.SysPermissionMapper;
import ycl.system.mapper.SysRoleMapper;
import ycl.system.mapper.SysUserMapper;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 登录接口 逻辑层
 *
 * @author ycl
 * @date 2022-03-09 16:35:45
 */
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {


	@Resource
	private SysUserMapper sysUserMapper;
	@Resource
	private SysRoleMapper sysRoleMapper;
	@Resource
	private SysPermissionMapper sysPermissionMapper;
	@Resource
	private StringRedisTemplate stringRedisTemplate;

	@Override
	public LoginUser login(HttpServletRequest request, UsernamePasswordReq req) {
		String username = req.getUsername();
		SysUser user = getUserByUsername(username);
		String password = SecureUtil.sha256(req.getPassword() + user.getSalt());
		if (!password.equals(user.getPassword()))
			throw new LoginException(ExceptionConstant.USER_ACCOUNT_PASSWORD_WRONG);
		return login(user, request.getHeader(SecurityConstant.LOGIN_TYPE), ServletUtil.getRemoteIp(request));
	}

	@Override
	//@Transactional
	public LoginUser login(HttpServletRequest request, String wechatId) {
		SysUser user = sysUserMapper.selectByWechatId(wechatId);
		if (user == null) {
			user = new SysUser();
			user.setWechatId(wechatId);
			register(user);
			user = sysUserMapper.selectById(user.getId());
		}
		return login(user, request.getHeader(SecurityConstant.LOGIN_TYPE), ServletUtil.getRemoteIp(request));
	}

	@Override
	public LoginUser login(SysUser sysUser, String loginType, String remoteAddress) {
		loginType = UserTypeEnum.getUserType(loginType);
		if (StrUtil.isBlank(remoteAddress))
			remoteAddress = "127.0.0.1";

		log.info("LoginServiceImpl -- loginType -> {}, remoteAddress -> {}", loginType, remoteAddress);
		sysUser.setPassword(null);
		LoginUser user = BeanConvertUtil.convert(sysUser, LoginUser.class);

		String src = user.getUsername() + remoteAddress + System.currentTimeMillis();
		String token = SecureUtil.sha256(src);
		Long userId = user.getId();
		//设置权限
		if (user.getType() == 0) {
			user.setIsAdmin(true);
			List<String> permissions = sysPermissionMapper.selectName();
			user.setPermissions(permissions);
		} else {
			List<Long> roleIds = sysRoleMapper.selectByUserId(userId);
			if (CollUtil.isNotEmpty(roleIds)) {
				user.setRoleIds(roleIds);
				//保存权限
				List<String> permissions = sysPermissionMapper.selectNameByRoleIds(roleIds);
				user.setPermissions(permissions);
			}
		}

		LocalDateTime now = LocalDateTime.now();
		sysUserMapper.updateLoginDetail(user.getId(), token, remoteAddress);
		user.setIsBindMobile(StrUtil.isNotBlank(user.getMobile()))
				.setIsAuth(StrUtil.isNotBlank(user.getIdCard()))
				.setToken(token)
				.setLoginIp(remoteAddress)
				.setLoginTime(now)
				.setLastLoginIp(sysUser.getLoginIp())
				.setLastLoginTime(sysUser.getLoginTime());

		//保存用户登录信息
		stringRedisTemplate.opsForValue().set(
				RedisConstant.getLoginUserKey(loginType, token),
				JSONObject.toJSONString(user),
				RedisConstant.LOGIN_EFFECTIVE_TIME,
				TimeUnit.HOURS);
		//同步删除上次登录信息
		if (StrUtil.isNotBlank(sysUser.getToken()))
			stringRedisTemplate.delete(RedisConstant.getLoginUserKey(loginType, sysUser.getToken()));

		//TODO 记录用户行为信息

		return user;
	}

	@Override
	public SysUser getUserByUsername(String username) {
		//查询此用户
		SysUser sysUser = sysUserMapper.selectByUsername(username);
		if (sysUser == null)//没有就根据手机号查
			sysUser = sysUserMapper.selectByMobile(username);
		if (sysUser == null)
			sysUser = sysUserMapper.selectByWechatId(username);
		if (sysUser == null) //没有这个用户, 账号或密码错误
			throw new LoginException(ExceptionConstant.USER_ACCOUNT_PASSWORD_WRONG);

		if (sysUser.getLocked())//用户被上锁
			throw new LoginException(ExceptionConstant.USER_IS_LOCKED);
		if (sysUser.getStatus() != 0)//用户被禁用
			throw new LoginException(ExceptionConstant.USER_DISABLE);
		if (StrUtil.isBlank(sysUser.getUsername()))
			sysUser.setUsername(sysUser.getMobile());
		return sysUser;
	}

	@Override
	@SuppressWarnings("all")
	public boolean selectExist(String s) {
		boolean b1 = sysUserMapper.selectExistByUsername(s);
		if (b1) return true;
		boolean b2 = sysUserMapper.selectExistByMobile(s);
		if (b2) return true;
		boolean b3 = sysUserMapper.selectExistByIdCard(s);
		if (b3) return true;
		boolean b4 = sysUserMapper.selectExistByWechatId(s);
		if (b4) return true;
		return false;
	}

	@Override
	public void logout(String token, String loginType, String remoteAddr) {
		stringRedisTemplate.delete(RedisConstant.getLoginUserKey(loginType, token));

		//TODO 记录用户行为信息
	}


	@Override
	@Transactional
	public void registerByMobilePassword(RegisterByMobilePassReq req) {
		String mobile = req.getMobile();
		String authCode = req.getAuthCode();
		String key = RedisConstant.getLoginUserKey(mobile);
		String value = stringRedisTemplate.opsForValue().get(key);
		Assert.notBlank(value, "验证码不存在或已过期");
		Assert.isTrue(authCode.equals(value), "验证码不正确");
		boolean exist = selectExist(mobile);
		Assert.isFalse(exist, "此用户已存在");
		SysUser user = BeanConvertUtil.convert(req, SysUser.class);
		register(user);
	}

	/**
	 * 注册公共部分
	 * 密码千万不能加密
	 *
	 * @param user
	 */
	@Transactional
	protected void register(SysUser user) {
		String salt = RandomUtil.randomStringUpper(20);
		String invitation;
		do {
			invitation = RandomUtil.randomStringUpper(10);
			boolean b = sysUserMapper.selectExistByInvitationCode(invitation);
			if (!b) break;
		} while (true);

		String password = user.getPassword();
		if (StrUtil.isNotBlank(password)) {
			password = SecureUtil.sha256(password + salt);
			user.setPassword(password);
		}
		user.setInvitationCode(invitation)
				.setSalt(salt);
		sysUserMapper.insert(user);
	}
}
